﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SD.Tools.Algorithmia.GeneralDataStructures;

namespace Util
{
    public static class EnumerableUtil
    {
        /// <summary>
        /// Returns a comma-separated list of the .ToString() of every value.
        /// </summary>
        public static string CommaSeparated<T>(this IEnumerable<T> xs)
        {
            return string.Join(", ", xs.Select(x => x.ToString()).ToArray());
        }

        /// <summary>
        /// Returns an IEnumerable with a single element.
        /// </summary>
        public static T[] Singleton<T>(this T x)
        {
            return new T[] { x };
        }
        /// <summary>
        /// Returns a List with a single element.
        /// </summary>
        public static List<T> SingletonList<T>(this T x)
        {
            return new List<T> { x };
        }
        /// <summary>
        /// Returns a SortedSet with a single element.
        /// </summary>
        public static SortedSet<T> SingletonSortedSet<T>(this T x)
        {
            return new SortedSet<T> { x };
        }
        public static IEnumerable<T> Concat<T>(this IEnumerable<IEnumerable<T>> xss)
        {
            return (from xs in xss
                    from x in xs
                    select x);
        }
        public static IEnumerable<T> Cons<T>(this T head, IEnumerable<T> tail)
        {
            yield return head;
            foreach (var x in tail)
                yield return x;
        }
        public static bool All(this IEnumerable<bool> xs)
        {
            return xs.All(b => b);
        }
        public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(
            this IEnumerable<IEnumerable<T>> left, IEnumerable<IEnumerable<T>> right)
        {
            return (from l in left
                    select (from r in right
                            select l.Concat(r)))
                   .Concat();
        }
        public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(
            this IEnumerable<IEnumerable<IEnumerable<T>>> xss)
        {
            IEnumerable<IEnumerable<T>> tmp = new List<T>().Singleton();
            foreach (var xs in xss)
            {
                tmp = tmp.CartesianProduct(xs);
            }
            return tmp;
        }
        public static IEnumerable<IEnumerable<T>> CartesianProductSingletons<T>(
            this IEnumerable<IEnumerable<T>> xss)
        {
            IEnumerable<IEnumerable<T>> tmp = new List<T>().Singleton();
            foreach (var xs in xss)
            {
                tmp = tmp.CartesianProduct(xs.Select(x => x.Singleton()));
                /*tmp = (from l in tmp
                       select (from r in xs.Select(x => new T[] { x })
                               select l.Concat(r)))
                      .Concat();*/
            }
            return tmp;
        }
        public static IEnumerable<KeyValuePair<K, SortedSet<T>>[]> CartesianProductSingletons<K, T>(
            this List<MultiValueDictionary<K, T>> xss) where K : class
        {
            int len = xss.Count;
            if (len == 0)
            {
                yield return new KeyValuePair<K, SortedSet<T>>[0];
                yield break;
            }
            int[] counts = xss.Select(xs => xs.Count).ToArray();
            int resLen = counts.Product();

            for (int i = 0; i < resLen; i++)
            {
                KeyValuePair<K, SortedSet<T>>[] current = new KeyValuePair<K, SortedSet<T>>[len];
                int tmp = i;
                for (int j = 0; j < len; j++)
                {
                    current[j] = xss[j].ElementAt(tmp % counts[j]);
                    tmp /= counts[j];
                }
                yield return current;
            }
        }
        public static IEnumerable<T[]> CartesianProductSingletons<T>(
            this List<SortedSet<T>> xss)
        {
            int len = xss.Count;
            if (len == 0)
            {
                yield return new T[0];
                yield break;
            }
            int[] counts = xss.Select(xs => xs.Count).ToArray();
            int resLen = counts.Product();

            for (int i = 0; i < resLen; i++)
            {
                T[] current = new T[len];
                int tmp = i;
                for (int j = 0; j < len; j++)
                {
                    current[j] = xss[j].ElementAt(tmp % counts[j]);
                    tmp /= counts[j];
                }
                yield return current;
            }
        }

        public static decimal Product(this IEnumerable<decimal> xs)
        {
            return xs.Aggregate((x, y) => x * y);
        }
        public static int Product(this IEnumerable<int> xs)
        {
            return xs.Aggregate((x, y) => x * y);
        }

        public static T ArgMin<T>(this IEnumerable<T> xs, Func<T, decimal> value)
        {
            T res = default(T);
            decimal? resVal = null;

            foreach (T x in xs)
            {
                decimal xVal = value(x);
                if (!resVal.HasValue || xVal < resVal.Value)
                {
                    res = x;
                    resVal = xVal;
                }
            }

            if (!resVal.HasValue)
            {
                throw new ArgumentException("Empty IEnumerable not allowed for ArgMin().");
            }

            return res;
        }

        private static bool ArgMax<T, U>(this IEnumerable<T> xs, Func<T, U> value, out T res)
                where U : IComparable<U>
        {
            res = default(T);
            U resVal = default(U);
            bool resValSet = false;

            foreach (T x in xs)
            {
                U xVal = value(x);
                if (!resValSet || xVal.CompareTo(resVal) > 0)
                {
                    res = x;
                    resVal = xVal;
                    resValSet = true;
                }
            }

            return resValSet;
        }
        public static T ArgMax<T, U>(this IEnumerable<T> xs, Func<T, U> value)
                where U : IComparable<U>
        {
            T res;
            if (!xs.ArgMax(value, out res))
            {
                throw new ArgumentException("Empty IEnumerable not allowed for ArgMax().");
            }
            return res;
        }
        public static T ArgMaxOrDefault<T, U>(this IEnumerable<T> xs, Func<T, U> value)
                where U : IComparable<U>
        {
            T res;
            xs.ArgMax(value, out res);
            // res = default(T) if xs is empty
            return res;
        }

        public static SortedSet<T> Union<T>(this IEnumerable<SortedSet<T>> xss)
        {
            SortedSet<T> res = new SortedSet<T>();
            foreach (var xs in xss)
            {
                res.UnionWith(xs);
            }
            return res;
        }
    }
}
